import os
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from PyQt5.QtCore import QObject, pyqtSignal

from src.utils import sanitize_filename, unique_path, build_session


class Downloader(QObject):
    progress_signal = pyqtSignal(int, int)
    log_signal = pyqtSignal(str)

    def __init__(self, urls, dest_dir, max_workers=4, timeout=30):
        super().__init__()
        # 支持 [url] 或 [(url, filename)]，按 url 去重但保留自定义文件名
        seen = set()
        cleaned = []
        for item in (urls or []):
            if isinstance(item, (list, tuple)) and item:
                key = str(item[0])
            else:
                key = str(item)
            if key in seen:
                continue
            seen.add(key)
            cleaned.append(item)
        self.urls = cleaned

        self.dest_dir = dest_dir
        self.max_workers = max_workers
        self.timeout = timeout
        os.makedirs(self.dest_dir, exist_ok=True)

    def _download_one(self, item) -> str:
        # 解析输入
        if isinstance(item, (list, tuple)) and len(item) >= 1:
            url = str(item[0])
            custom_name = str(item[1]) if len(item) > 1 and item[1] else None
        else:
            url = str(item)
            custom_name = None

        # 选择文件名：优先自定义；否则从 URL 推断
        name = custom_name or os.path.basename(url.split("?", 1)[0]) or "audio.mp3"
        # 清洗并去重路径
        safe_name = sanitize_filename(name)
        out_path = unique_path(self.dest_dir, safe_name)

        session = build_session()
        try:
            with session.get(url, stream=True, timeout=(5, self.timeout)) as r:
                r.raise_for_status()
                with open(out_path, "wb") as f:
                    for chunk in r.iter_content(64 * 1024):
                        if chunk:
                            f.write(chunk)
            return out_path
        finally:
            try:
                session.close()
            except Exception:
                pass

    def start(self):
        total = len(self.urls)
        if total == 0:
            self.log_signal.emit("[INFO] 无下载任务")
            self.progress_signal.emit(0, 0)
            return

        try:
            from pathlib import Path
            mod_path = str(Path(__file__).resolve())
        except Exception:
            mod_path = __file__
        self.log_signal.emit(f"[DEBUG] downloader module: {mod_path}")

        workers = max(1, int(self.max_workers or 0))
        self.log_signal.emit(f"[INFO] 开始下载，共 {total} 个文件")
        self.log_signal.emit(f"[INFO] 提交任务: {total} 个, max_workers={workers}")

        done_lock = threading.Lock()
        done_count = 0

        try:
            with ThreadPoolExecutor(max_workers=workers, thread_name_prefix="voice-dl") as ex:
                future_to_url = {}
                for idx, item in enumerate(self.urls, 1):
                    # 取出 url 仅用于日志
                    url = item[0] if isinstance(item, (list, tuple)) else item
                    self.log_signal.emit(f"[DEBUG] submit {idx}/{total}")
                    fut = ex.submit(self._download_one, item)
                    future_to_url[fut] = str(url)

                for fut in as_completed(future_to_url):
                    url = future_to_url[fut]
                    try:
                        out_path = fut.result()
                        if out_path and os.path.exists(out_path):
                            self.log_signal.emit(f"[INFO] 下载完成: {os.path.basename(out_path)}")
                        else:
                            self.log_signal.emit(f"[WARN] 下载返回空结果: {url}")
                    except Exception as e:
                        self.log_signal.emit(f"[ERROR] 下载失败: {url} -> {e}")
                    finally:
                        with done_lock:
                            done_count += 1
                            self.progress_signal.emit(done_count, total)
        except Exception as e:
            self.log_signal.emit(f"[ERROR] 线程池创建或任务提交失败: {e}")

        self.log_signal.emit("[INFO] 全部下载任务完成")
